GO
1. Shell中特殊且重要的变量
1.1. Shell中的特殊位置参数变量
在Shell中存在一些特殊且重要的变量,例如:$0
、$1
、$#
等,我们称之为特殊位置参数变量。要从命令行、函数或脚本执行等处传递参数时,就需要在Shell脚本中使用位置参数变量。下表为常用的特殊位置参数变量的说明:
位置变量 | 作用说明 |
---|---|
$0 | 获取当前执行的Shell脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径 |
$n | 获取当前执行的Shell脚本的第n个参数,n=1..9,当n为0时表示脚本的文件名;如果n大于9,则要用大括号括起来,例如${10} ,接的参数以空格隔开 |
$# | 获取当前执行的Shell脚本后面接的参数的总个数 |
$* | 获取当前Shell脚本所有传参的参数,不加引号和$@ 相同;如果给$* 加上双引号,例如:"$*" ,则表示将所有的参数视为单个字符串,相当于$1 $2 $3 |
$@ | 获取当前Shell脚本所有传参的参数,不加引号和$* 相同;如果给它加上双引号,例如:$@ ,则表示将所有的参数视为不同的独立字符串,相当于"$1" "$2" "$3" "..." 。这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在每个参数里的任何空白。当$@ 和$* 都加双引号时,两者是有区别的;都不加双引号时,两者无区别。 |
1.1.1. $n的用法总结
- 加引号括起来的内容传参,会作为一个字符串参数,如:
sh test.sh "theshu boy"
中theshu boy
作为一个参数 - 当
$n
的n大于9时,必须要这样用才能得到正确的结果:${n}
,示例代码如下:12345678910111213141516[theshu@theshu ~]$ echo \${1..15} #<==利用大括号输出15个位置参数,学会了该命令就不用手敲代码了。$1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15[theshu@theshu ~]$ echo \${1..15} > n.sh #<==将15个位置参数定向到文件n.sh[theshu@theshu ~]$ vim n.sh #<==在代码的最前面添加echo[theshu@theshu ~]$ cat n.sh #<==最终的测试代码echo $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15[theshu@theshu ~]$ echo {a..z} #<=测试打印26个英文字母并以空格分隔a b c d e f g h i j k l m n o p q r s t u v w x y z[theshu@theshu ~]$ sh n.sh {a..z} #<==传入26个英文字母,以空格分隔作为参数a b c d e f g h i a0 a1 a2 a3 a4 a5 #<==位置参数大于9后,输出的内容就不对了[theshu@theshu ~]$ vim n.sh #<==修改代码[theshu@theshu ~]$ cat n.sh #<==最终代码如下echo $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11} ${12} ${13} ${14} ${15}[theshu@theshu ~]$ sh n.sh {a..z} #重新测试a b c d e f g h i j k l m n o #<==位置参数大于9的也正确了
1.1.2. $0特殊变量的作用及变量实践
$0
的作用为取出执行脚本的名称(包括路径)。若不带路径执行脚本,那么输出结果就是脚本的名字,若使用绝对路径执行脚本,那么输出结果就是全路径加上脚本的名字。示例代码如下:123456[theshu@theshu ~]$ cat n.shecho $0[theshu@theshu ~]$ sh n.shn.sh[theshu@theshu ~]$ sh /home/theshu/n.sh/home/theshu/n.sh当执行的脚本为绝对路径时,$0也会带着路径,这时如果希望单独获取名称或路径,可以利用
dirname
和basename
这两个命令。示例代买如下:1234567891011[theshu@theshu ~]$ dirname /home/theshu/n.sh/home/theshu #<==dirname命令的作用是获取脚本的路径[theshu@theshu ~]$ basename /home/theshu/n.shn.sh #<==basename命令的作用是获取脚本的名字#<==利用$0和dirname以及basename命令分别取出脚本名称和脚本路径的代码:[theshu@theshu ~]$ cat n.shdirname $0basename $0[theshu@theshu ~]$ sh /home/theshu/n.sh/home/theshun.sh有关
$0
这个位置参数的系统生产场景案例如下,其中采用aegis系统脚本:1234567891011[theshu@theshu ~]$ tail -6 init.d/aegisecho $"Usage: $0 {start|stop|restart|status|uninstall}"exit 1;;esac[theshu@theshu ~]$ sudoinit.d/aegisUsage: init.d/aegis {start|stop|restart|status|uninstall}
1.1.3. $#特殊变量获取脚本传参个数的实践
例子:通过$#
获取脚本传参的个数。
下面是一个针对$0
、$1
、$#
等多位置参数的综合型企业案例,它的作用是根据用户在命令行的传参个数判断用户的输入,不合要求的给予提示并退出。
首先来看条件表达式判断语句的写法,如下:
然后是if判断语句的写法,如下:
1.1.4. $*和$@特殊变量功能及区别说明
例子:利用set
命令设置位置参数(同命令行脚本的传参)
测试$*
和$@
,注意,此时不带双引号:
测试$*
和$@
,注意,此时带有双引号:
可以利用shift
将位置参数移位(左移):
1.2. Shell进程中的特殊状态变量
Shell进程中的特殊状态变量如下表:(提示:下表的查找方式为使用man bash
命令,然后搜索关键字Special Parameteers
)
位置变量 | 作用说明 |
---|---|
$? | 获取执行上一个指令的执行装填返回值(0为成功,非零为失败),这个变量最常用 |
$$ | 获取当前执行的Shell脚本的进程号(PID),这个变量不常用,了解即可 |
$! | 获取上一个在后台工作的进程的进程号(PID),这个变量不常用,了解即可 |
$_ | 湖区在此之前执行的命令或脚本的最后一个参数,这个变量不常用,了解即可 |
1.2.1. 关于$?的说明
- 在不通命令的执行结果中,
$?
的返回值不尽相同,但在工作场景中,常用的就是0和非0两种状态,0表示成功运行,非0表示运行失败。 - 若使用源码编译安装软件,可以在每个步骤的结尾获取
$?
来判断命令执行成功与否。 - 对于新手来说,在安装服务时,可以通过获取执行命令的返回值来确定命令的执行状态,从而快速确定命令是否执行成功。不过,有经验的技术人员不需要获取返回值,通过命令的最后过程输出就可以快速判断是否成功。
- 当对服务器的数据进行备份时,我们会在执行完关键命令,例如
tar
或cp
后,通过获取返回值来判断命令是否执行成功,备份数据是否完整。
在企业场景下,$?
返回值的用法如下:
- 判断命令、脚本或函数等程序是否执行成功
- 若在脚本中调用执行
exit 数字
,则会返回这个数字给$?
变量 - 如果是在函数里,则通过
return 数字
把这个数字以函数返回值的形式传给$?
1.2.2. 关于$$的应用
例子:实现系统中多次执行某一个脚本后的进程只有一个(此为$$的企业级应用)
说明:有时执行定时任务脚本的频率比较快,并不知道上一个脚本是否真的执行完毕,但是,业务要求同一时刻只能有一个同样的脚本在运行,此时就可以利用$$
获取上一次运行的脚本进程号,当程序重新运行时,根据获得的进程号,清理掉上一次的进程,运行新的脚本命令。脚本如下:
提示:这是一个生产案例的简单模拟,脚本用于执行启动或定时任务时,相同的脚本中只能有一个在运行,当新脚本运行时,必须关闭未完成或未退出的上一次的同名脚本进程。
1.2.3. 关于$_的说明及实践
$_
的作用是获得上一条命令的最后一个参数值,此功能用的不多,了解即可。示例如下:
1.2.4. 关于$!的说明及实践
$!
的功能类似与$$
,只不过作用是获取上一次执行脚本的pid,对此,了解即可。范例如下:
2. bash Shell内置变量命令
bash Shell包含一些内置命令,它们是由Shell本身提供的。常用的内部命令有:echo、eval、exec、export、read、shift。下面简单介绍几个最常用的内置命令的格式和功能。
2.1. echo在屏幕上输出信息
- 命令格式:
echo args
(可以是字符串和变量的组成) - 功能说明:将
echo
命令后面args
指定的字符串及变量等显示到标准输出 - 常用参数见下表:
echo参数选项 | 说明 |
---|---|
-n | 不换行输出内容 |
-e | 解析转义字符(见下面的字符) |
转义字符 | |
\n | 换行 |
\r | 回车 |
\t | 制表符(tab) |
\b | 退格 |
\v | 纵向制表符 |
参数的应用示例:
printf
与echo
的功能类似,但是printf更强大,当需要特殊复杂的格式时才考虑使用printf
。
2.2. eval(后文有案例讲解)
- 命令格式:
eval args
- 功能:当Shell程序执行到eval语句时,Shell读入参数args,并将它们组合成一个新的命令,然后执行。
- 范例如下:123456789[theshu@theshu ~]$ cat noeval.shecho \$$#[theshu@theshu ~]$ sh noeval.sh arg1 arg2$2[theshu@theshu ~]$ cat noeval.sheval "echo \$$# "#<==加上eval命令,使得打印的也是位置参数,重新解析输出,而不是输出$2本身。[theshu@theshu ~]$ sh noeval.sh arg1 arg2arg2 #<==输出了$2的值
2.3. exec
- 命令格式:
exec 命令参数
- 功能:
exec
命令能够在不创建新的子进程的前提下,转去执行指定的命令,当指定的命令执行完毕后,该进程(也就是最初的Shell)就终止了,示例如下:123[root@theshu ~]# exec dateSun Feb 25 20:24:18 CST 2018[theshu@theshu ~]$ #<==退到普通用户模式下了
当使用exec打开文件后,read命令每次都会将文件指针移动到文件的下一行进行读取,知道文件末尾,利用这个可以实现处理文件内容。exec的功能示例:
2.4. read
- 命令格式:
read 变量名列表
- 功能:从标准输入读取字符串等信息,传给Shell程序内部定义的变量。
此命令将在后文详细讲解。
2.5. shift
- 命令格式:
shift
- Shift positional parameters
- 功能:shift语句会按如下方式重新命令所有的位置参数变量,即
$2
成为$1
、$3
称为$2
等,以此类推,在程序中每使用一次shift语句,都会使所有的位置参数一次向左移动一个位置,并使位置参数$#
减1,直到减到0为止。 - shift功能介绍:123456789[theshu@theshu ~]$ help shiftshift: shift [n]Shift positional parameters.Rename the positional parameters $N+1,$N+2 ... to $1,$2 ... If N isnot given, it is assumed to be 1.Exit Status:Returns success unless N is negative or greater than $#.
shift命令的主要作用是将位置参数$1、$2等进行左移,即如果位置参数是$3、$2、$1,那么执行一次shift后,$3就变成了$2、$2变成了$1,$1就消失了。
shift命令的使用示例:
应用场景:当我们写Shell希望像命令行的命令通过参数控制不同的功能时,就会先传一个类似-c的参数,然后再接内容。
2.6. exit
- 命令格式:
exit
- Exit the shell
- 功能:退出Shell程序。在exit之后可以有选择地指定一个数位作为返回状态。
3. Shell变量子串知识及实践
3.1. Shell变量子串介绍
Shell变量字串的常用操作见下表。可以执行man bash
命令之后,搜索Parameter Expansion
找到相应的帮助知识,对于Shell新手来说,次部分内容可以暂时忽略,可以学完全部内容之后再学这一部分即可。
序号 | 表达式 | 说明 |
---|---|---|
1 | ${parameter} |
返回变量$parameter 的内容 |
2 | ${井parameter} |
返回变量$parameter 内容的长度(按字符),也适用于特殊变量 |
3 | ${parameter:offset} |
在变量${parameter} 中,从位置offset之后开始提取字串到结尾 |
4 | ${parameter:offset:length} |
在变量${parameter} 中,从位置offset之后开始提取长度为length的字串 |
5 | ${parameter#word} |
从变量${parameter} 开头开始删除最短匹配的word子串 |
6 | ${parameter##word} |
从变量${parameter} 开头开始删除最长匹配的word子串 |
7 | ${parameter%word} |
从变量${parameter} 结尾开始删除最短匹配的word子串 |
8 | ${parameter%%word} |
从变量${parameter} 结尾开始删除最长匹配的word子串 |
9 | ${parameter/pattern/string} |
使用string代替第一个匹配的pattern |
10 | ${parameter//pattern/string} |
使用string代替所有匹配的pattern |
3.2. Shell变量子串的实践
准备:定义THESHU变量,赋值内容为”I am theshu”,操作代码如下:
12345[theshu@theshu ~]$ THESHU="I am theshu"[theshu@theshu ~]$ echo ${THESHU}I am theshu[theshu@theshu ~]$ echo $THESHUI am theshu例:返回THESHU变量值的长度。通过在变量名前加#,就可以打印变量值的长度:
12[theshu@theshu ~]$ echo ${井THESHU}11例:Shell的其它打印变量长度的方法。
123456[theshu@theshu ~]$ echo $THESHU | wc -L11 #<==输出变量值,然后通过管道交给wc计算长度[theshu@theshu ~]$ expr length "$THESHU"11 #<==利用expr的length函数计算变量长度[theshu@theshu ~]$ echo "$THESHU" | awk '{print length($0)}'11 #<==利用awk的length函数计算变量长度,也可无"($0)"这几个字符
提示:上述计算变量长度的方法中,变量的子串方式是最快的,即
${井THESHU}
- 例:利用
time
命令及for循环对几种获取字符串长度的方法进行性能比较
变量自带的获取长度的方法(
echo ${井char}
)12345[theshu@theshu ~]$ time for n in {1..10000};do char=`seq -s "theshu" 100`;echo ${#char} &> /dev/null;donereal 0m8.472s #<==变量自带的获取长度的方法用时最少,效率最高user 0m5.331ssys 0m3.099s利用管道加
wc
的方法(echo ${cahr} | wc -L
)12345[theshu@theshu ~]$ time for n in {1..10000};do char=`seq -s "theshu" 100`;echo ${char} | wc -L &> /dev/null;donereal 0m27.965s #<==使用了管道加wc -L计算,结果倒数第二,仅次于管道加awk统计的user 0m15.910ssys 0m11.917s利用
expr
自带的length方法(expr length "${char}"
)12345[theshu@theshu ~]$ time for n in {1..10000};do char=`seq -s "theshu" 100`;expr length "${cahr}" &> /dev/null;donereal 0m15.283s #<==好于使用管道和wc的计算方法,但是比变量自带的获取长度的方法要差一些user 0m9.217ssys 0m5.990s利用
awk
自带的length函数方法12345[theshu@theshu ~]$ time for n in {1..10000};do char=`seq -s "theshu" 100`;echo $char | awk '{print length($0)}' &> /dev/null;donereal 0m30.363s #<==使用了管道还有awk的函数计算,结果最差user 0m16.500ssys 0m13.709s
可以看到,这几种方法的速度相差几十到上百倍,一般情况下调用外部命令来处理的方式与使用内置操作的速度相差较大。在Shell编程中,应尽量使用内置操作或函数来完成。
有关获取字符串长度的几种统计方法的性能比较如下:
- 变量自带的计算长度的方法的效率最高,在要求效率的场景中尽量多用
- 使用管道统计的方法的效率都比较差,在要求效率的场景中尽量不用
对于日常简单的脚本计算,读者可以根据自己所擅长的或易用的程度去选择
例:截取THESHU变量的内容,从第2个字符之后开始截取,默认截取后面字符的全部,第2个字符不包含在内,也可理解为删除前面的多个字符
1234[theshu@theshu ~]$ echo ${THESHU}I am theshu[theshu@theshu ~]$ echo ${THESHU:2}am theshu #<==相当于从I后面的空格开始计算,截取到了结尾例:截取THESHU变量的内容,从第2个字符之后开始截取,截取2个字符
12345[theshu@theshu ~]$ echo ${THESHU:2:2}am提示:这个功能类似于cut命令-c参数的功能,如下;[theshu@theshu ~]$ echo ${THESHU} | cut -c 3-4am例:从变量$THESHU内容的开头开始删除最短匹配
"a*C"
及"a*c"
的子串1234567[theshu@theshu ~]$ THESHU=abcABC123ABCabc[theshu@theshu ~]$ echo $THESHUabcABC123ABCabc[theshu@theshu ~]$ echo ${THESHU#a*C} #<==从开头开始删除最短匹配"a*C"的子串123ABCabc #<==从开头开始删除了abcABC[theshu@theshu ~]$ echo ${THESHU#a*c}ABC123ABCabc #<==从开头开始删除了abc例:从变量$THESHU开头开始删除最长匹配
"a*C"
及"a*c"
的子串1234567[theshu@theshu ~]$ THESHU=abcABC123ABCabc[theshu@theshu ~]$ echo $THESHUabcABC123ABCabc[theshu@theshu ~]$ echo ${THESHU##a*c} #<==从开头开始删除最长匹配"a*c"的子串#<==结果为空了,说明都匹配了,全部都删除了[theshu@theshu ~]$ echo ${THESHU##a*C} #<==从开头开始删除最长匹配"a*C"的子串abc #<==结果为abc,说明匹配了abcABC123ABC并删除了这些字符例:从变量$THESHU结尾开始删除最短匹配
"a*C"
及"a*c"
的子串1234567[theshu@theshu ~]$ THESHU=abcABC123ABCabc[theshu@theshu ~]$ echo $THESHUabcABC123ABCabc[theshu@theshu ~]$ echo ${THESHU%a*C} #<==从结尾开始删除最短匹配"a*c"的子串abcABC123ABCabc #<==原样输出,因为从结尾开始"a*C"没有匹配上任何子串,因此,没有删除任何字符[theshu@theshu ~]$ echo ${THESHU%a*c} #<==从结尾开始删除最短匹配"a*c"的子串abcABC123ABC #<==从结尾开始删除最短匹配"a*c",即删除了结尾的abc三个字符例:从变量$THESHU结尾开始删除最长匹配
"a*C"
及"a*c"
的子串1234567[theshu@theshu ~]$ THESHU=abcABC123ABCabc[theshu@theshu ~]$ echo $THESHUabcABC123ABCabc[theshu@theshu ~]$ echo ${THESHU%%a*C} #<==从结尾开始删除最长匹配"a*C"的子串abcABC123ABCabc #<==原样输出,因为从结尾开始"a*C"没有匹配上任何子串,因此,没有删除任何字符[theshu@theshu ~]$ echo ${THESHU%%a*c} #<==从结尾开始删除最长匹配"a*c"的子串#<==从结尾开始删除最长匹配"a*C"的字符串,即删除全部字符
有关上述匹配删除的小结:
#
表示从开头删除匹配最短##
表示从开头删除匹配最长%
表示从结尾删除匹配最短%%
表示从结尾删除匹配最长a*C
表示匹配的字符串,*
表示匹配所有,a*C
匹配开头为a、中间为任意多个字符、结尾为C的字符串a*c
表示匹配的字符串,*
表示匹配所有,a*c
匹配开头为a、中间为任意多个字符、结尾为c的字符串例:使用thewang字符代替变量$THESHU匹配的theshu字符串
1234567[theshu@theshu ~]$ THESHU="I am theshu,yes,theshu"[theshu@theshu ~]$ echo $THESHUI am theshu,yes,theshu[theshu@theshu ~]$ echo ${THESHU/theshu/thewang} #<==替换匹配的第一个字符串I am thewang,yes,theshu[theshu@theshu ~]$ echo ${THESHU//theshu/thewang} #<==替换匹配的所有字符串I am thewang,yes,thewang
有关替换的小结:
- 一个
/
表示替换匹配的第一个字符串 - 两个
/
即//
表示替换匹配的所有字符串
3.3. 变量子串的生产场景应用案例
可以用来批量修改文件名:假如本目录里有一些文件,这些文件的名字当中都有theshu这个字符串,批量修改去掉它:
4. Shell特殊扩展变量的知识与实践
4.1. Shell特殊扩展变量介绍
Shell的特殊扩展变量说明见下表,也可以执行man bash
命令,然后搜索Parameter Expansion
查找相关的帮助内容。
表达式 | 说明 |
---|---|
${parameter:-word} |
如果parameter 的变量值为空或未赋值,则会返回word 字符串并替代变量的值。用途:如果变量未定义,则返回备用的值,防止变量为空值或因未定义而导致异常 |
${parameter:=word} |
如果parameter 的变量值为空或未赋值,则设置这个变量值为word ,并返回其值。位置变量和特殊变量不适用。用途:基本上同一个${parameter:-word},但该变量又额外给parameter 变量赋值了 |
${parameter:?word} |
如果parameter 变量值为空或未赋值,那么word 字符串将被作为标准错误输出,否则输出变量的值。用途:用于捕捉由于变量未定义而导致的错误,并退出程序 |
${parameter:+word} |
如果parameter 变量值为空或未赋值,则什么都不做,否则word 字符串将替代变量的值 |
在上表中,每个表达式内的冒号都是可选的。如果省略了表达式中的冒号,则将每个定义中的为空或未赋值
部分改为未赋值
,也就是说,运算符仅用于测试变量是否未赋值。
4.2. Shell特殊扩展变量的实践
4.2.1. ${parameter:-word}
功能实践
${parameter:-word}
的作用是如果parameter变量值为空或未赋值,则会返回word字符串替代变量的值。
示例1:
结论:对于${test:-UNSET}
,当test变量没有值时,就返回变量结尾设置的UNSET字符串。
示例2:
结论:当test有值时,就打印result变量,返回test变量的内容。
4.2.2. ${parameter:=word}
功能实践
${parameter:=word}
的作用是:如果parameter变量值为空或未赋值,就设置这个变量值为word,并返回其值。位置变量和特殊变量不适用。
示例:
当变量result
值里的变量test
值没有定义时,会给变量result
赋值:=
后面的内容,同时会把:=
后面的内容赋值给变量result
值里没有定义的变量test
。
这个变量的功能可以解决变量没有定义的问题,并确保没有定义的变量始终有值。
4.2.3. ${parameter:?word}
功能实践
${parameter:?word}
的作用是:如果parameter变量值为空或未赋值,那么word字符串将被作为标准错误输出,否则输出变量的值。
范例:
本例的用法可以用于设定由于变量未定义而报错的具体内容,如:”not defined”。
4.2.4. ${parameter:+word}
功能实践
${parameter:+word}
的作用是:如果parameter变量值为空或未赋值,则什么都不做,否则word字符串将替代变量的值。
范例:
此功能可用于测试变量(wang
的位置)是否存在,如果theshu
的值为word
,则证明wang
变量有值。
4.3. Shell特殊扩展变量的生产场景应用案例
一个生产案例:删除7天前的过期的数据备份:
如果忘记了定义
path
变量,又不希望path值为空值,就可以定义/tmp
替代path
空值的返回值,如下:123456[root@theshu ~]# cat del.shfind ${path-/tmp} -name "*.tar.gz" -type f -mtime +7 | xargs rm -f[root@theshu ~]# sh -x del.sh+ find /tmp -name '*.tar.gz' -type f -mtime +7+ xargs rm -f#<==执行时,系统会自动删除/tmp下的文件如果忘了定义path变量,并且还未做特殊变量定义,那么命令就会出现意外,如下:
12345[root@theshu ~]# cat a.shfind ${path} -name "*.tar.gz" -type f -mtime +7 | xargs rm -f[root@theshu ~]# sh -x a.sh+ find -name '*.tar.gz' -type f -mtime +7 #<==这条命令明显没有指定路径,因此将会导致异常+ xargs rm -f
OK